/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "asf-tools/asf-gen/TypeConversionGenerator.h"
#include "asf-tools/GeneralServiceGeneratorExtensions.h"
#include "model/FModelManager.h"
#include "utilities/string_utility.h"
#include <fstream>
#include <iostream>

namespace BstASF
{
static const std::string TypeConversionHeaderTpl = R"({LISENCE}
/*
 * This file was generated by the BST IDL Generator.
 * All manual modifications will be LOST by next generation.
 * It is recommended NOT modify it.
 * Generator Version: fidl-generator $GEN_VER
 */

#ifndef TYPE_CONVERSION_H
#define TYPE_CONVERSION_H

{INCLUDE_HEADERS}
#include "nlohmann311/json.hpp"
#include <tuple> 

#if !defined (COMMONAPI_INTERNAL_COMPILATION)
#define COMMONAPI_INTERNAL_COMPILATION
#define HAS_DEFINED_COMMONAPI_INTERNAL_COMPILATION_HERE
#endif

#include <CommonAPI/Enumeration.hpp>
#include <CommonAPI/RangedInteger.hpp>
#include <CommonAPI/Variant.hpp>

#if defined (HAS_DEFINED_COMMONAPI_INTERNAL_COMPILATION_HERE)
#undef COMMONAPI_INTERNAL_COMPILATION
#undef HAS_DEFINED_COMMONAPI_INTERNAL_COMPILATION_HERE
#endif

NLOHMANN_JSON_NAMESPACE_BEGIN

template<typename value_type>
void getJson(json &j, const value_type &V)
{
    if (std::is_same<value_type, std::string>::value)
    {
        j = json(V);
    }
    else if (std::is_arithmetic<value_type>::value)
    {
        j = json(V);
    }
    else if (std::is_class<value_type>::value)
    {
        adl_serializer<value_type>().to_json(j, V);
    }
}
{CONVERSION_CONTENT}
NLOHMANN_JSON_NAMESPACE_END

#endif
)";

static const std::string ErrorAndEnumConversion = R"(
template<typename T>
struct adl_serializer<T, std::enable_if_t<std::is_base_of<CommonAPI::Enumeration< uint8_t>, T>::value> >
{
    static void to_json(json& j, const T& opt)
    {
        std::string literal = opt.toString();
        if (literal == "UNDEFINED")
            j = nullptr;
        else
            getJson<uint8_t>(j, opt.value_);
    }

    static void from_json(const json& j, T& opt)
    {
        if(j == nullptr)
             return;
        auto value = j.get<uint8_t>();
        opt = T();
        opt.value_ = value;
    }
};
)";

static const std::string StructConvertTpl = R"(
template<>
struct adl_serializer<{STRUCT_NAMESPACE}>
{
    static void to_json(json& j, const {STRUCT_NAMESPACE}& opt)
    {
        {STRUCT_GET_FIELD_VALUE}

        {GET_JSON_OBJ}

        {CONVERT_TO_JSON}

    }

    static void from_json(const json& j, {STRUCT_NAMESPACE}& opt)
    {
        {TYPEDEF_FIELD_TYPE}

        {GET_VAL_FROM_json}

        opt = {STRUCT_NAMESPACE}({PARAMETERS_LIST});
    
    }
};
)";

static const std::string UnionConversion = R"(
// for union variant
template<typename ... Fields>
struct GetVariantFromJson
{
    GetVariantFromJson(const int &size_,const json &j):size(size_), j_val(j)
    {}

    template<typename T, typename ... partFields, typename std::enable_if<std::tuple_size<std::tuple<partFields ...>>::value != 0 >::type * = nullptr >
    CommonAPI::Variant<Fields ... > getVariant(const std::size_t i)
    {
        std::size_t num = std::tuple_size<std::tuple<partFields ...>>::value;
        if (i + num > size)
        {
            return getVariant<partFields ...>(i);
        }
        else if (i + num == size)
            return CommonAPI::Variant<Fields ... >(j_val.at("value").get<T>());
        else
            return CommonAPI::Variant<Fields ... >();
    }

    template<typename T>
    CommonAPI::Variant<Fields ... > getVariant(const std::size_t i)
    {
        if (i == size)
        {
            return CommonAPI::Variant<Fields ... >(j_val.at("value").get<T>());
        }
        else
            return CommonAPI::Variant<Fields ... >();
    }

    const std::size_t size;
    const json j_val;
};

template<typename ... Fields>
struct GetJsonFromValue
{
    GetJsonFromValue(const CommonAPI::Variant<Fields ... >& obj):opt(obj)
    {}

    template<typename T, typename ... partFields, typename std::enable_if<std::tuple_size<std::tuple<partFields ...>>::value != 0 >::type * = nullptr >
    json getVariantJson()
    {
        if (opt.template isType<T>())
        {
            json val;
            getJson<T>(val, opt.template get<T>());
            return val;
        }
        else
            return getVariantJson<partFields ...>();
    }

    template<typename T>
    json getVariantJson()
    {
        if (opt.template isType<T>())
        {
            json val;
            getJson<T>(val, opt.template get<T>());
            return val;
        }
        else
            return nullptr;
    }

   const CommonAPI::Variant<Fields ... >& opt;
};

template<typename ... Fields>
struct adl_serializer< typename CommonAPI::Variant<Fields ...>>
{
    static void to_json(json& j, const CommonAPI::Variant<Fields ... >& opt)
    {
        if (!opt.hasValue())
        {
            j = nullptr;
            return;
        }

        json type;
        GetJsonFromValue<Fields ...> obj(opt);
        json val = obj.template getVariantJson<Fields ...>();
        getJson<uint8_t>(type, opt.template getValueType());
        j.push_back({"type", type});
        j.push_back({"value", val});
    }

    static void from_json(const json& j, CommonAPI::Variant<Fields ... >& opt)
    {
        if (j == nullptr)
            return;

        uint8_t index = j.at("type").get<uint8_t>();
        GetVariantFromJson<Fields ...> obj(index, j);
        opt = obj.template getVariant<Fields ...>(index);
    }
};
)";

static const std::string MapConversion = R"(
template<typename Key, typename Value>
struct adl_serializer<typename std::unordered_map<Key, Value>>
{
    static void to_json(json& j, const std::unordered_map<Key, Value>& opt) 
    {
        j = json(json::value_t::array);
        for (auto it = opt.cbegin(); it != opt.cend(); ++it)
        {
            json tmp_key, tmp_val;
            getJson<Key>(tmp_key, it->first);
            getJson<Value>(tmp_val, it->second);
            j.push_back({{"key", tmp_key}, {"val", tmp_val}});
        }
    }

    static void from_json(const json& j, std::unordered_map<Key, Value>& opt)
    {
        if (!opt.empty())
            opt.clear();

        for (auto i = 0; i < j.size(); ++i)
        {
            auto object_pair = j.at(i);
            if (object_pair == nullptr)
                continue;
            auto key = object_pair.at("key").get<Key>();
            auto val = object_pair.at("value").get<Value>();
            opt[key] = val;
        }
    }
};
)";

static const std::string ArrayConversion = R"(
template<typename T>
struct adl_serializer<std::vector<T>, std::enable_if_t<!std::is_arithmetic<T>::value && !std::is_same<T, std::string>::value && !std::is_same<T, std::vector<unsigned char>>::value> >
{
    static void to_json(json& j, const std::vector<T>& opt)
    {
        int index = 0;
        for (const auto &item : opt)
        {
            json tmp;
            getJson<T>(tmp, opt[index]);
            ++index;
            j[std::to_string(index)].push_back(tmp);
        }
    }

    static void from_json(const json& j, std::vector<T>& opt)
    {
        if (!opt.empty())
            opt.clear();

        for (int index = 0; j.contains(std::to_string(index)); ++index)
        {
            opt[index] = j.at(std::to_string(index)).get<T>();
        }
    }
};
)";

static const std::string IntegerCoversion = R"(
template<int minimum, int maximum>
struct adl_serializer<typename CommonAPI::RangedInteger<minimum, maximum>>
{
    static void to_json(json& j, const CommonAPI::RangedInteger<minimum, maximum>& opt)
    {
        j = json(int(opt));
    }

    static void from_json(const json& j, CommonAPI::RangedInteger<minimum, maximum>& opt)
    {
        opt = CommonAPI::RangedInteger<minimum, maximum>(j.get<int>());
    }
};
)";

void TypeConversionGenerator::generateConversionFile(const std::string &dir)
{
    auto &gen = BstASF::GeneralServiceGeneratorExtensions::getInstance();
    auto configHeaderPath = gen.getTypeConversionPath();
    auto d_h_path = mk_dir(dir, configHeaderPath);
    if (d_h_path.empty())
        return;

    d_h_path += "/" + gen.getTypeConversionHeaderFile();
    std::ofstream OsWriteH(d_h_path, std::ofstream::out);
    auto convertHeader = generateConversionHeader();
    OsWriteH << replace_all(convertHeader, "\t", "    ");
    OsWriteH.close();
}

std::string TypeConversionGenerator::generateConversionHeader()
{
    std::string content;
    auto &gen = BstASF::GeneralServiceGeneratorExtensions::getInstance();
    content = TypeConversionHeaderTpl;
    // INCLUDE HEADERS: tc & inf
    // only tc & inf  struct need
    // logic default & stubimpl
    std::list<std::shared_ptr<BstIdl::FModel>> models_contain_RS;
    std::list<std::string> include_headers;
    std::list<std::string> all_type_tpls;
    std::list<std::shared_ptr<BstIdl::FInterface>> fInterfaces = gen.getRSInterfaces();
    all_type_tpls.emplace_back(IntegerCoversion);
    all_type_tpls.emplace_back(ErrorAndEnumConversion);
    for (const auto &inf : fInterfaces)
    {
        auto model = std::dynamic_pointer_cast<BstIdl::FModel>(inf->getContainer());
        if (model)
            addListItem(models_contain_RS, model);

        getHeaders(inf, include_headers);
        all_type_tpls.splice(all_type_tpls.end(), generateStructConversion(inf));
    }

    for (const auto &model : models_contain_RS)
    {
        for (const auto &tc : model->getTypeCollections())
        {
            all_type_tpls.splice(all_type_tpls.end(), generateStructConversion(tc));
        }
    }
    all_type_tpls.emplace_back(UnionConversion);
    all_type_tpls.emplace_back(MapConversion);
    all_type_tpls.emplace_back(ArrayConversion);
    content = replace_all(content, "{LISENCE}", getLicense());
    content = replace_all(content, "$GEN_VER", COMMIT_HASH);
    content = replace_all(content, "{INCLUDE_HEADERS}", join(include_headers, "\n"));
    content = replace_all(content, "{CONVERSION_CONTENT}", join(all_type_tpls, ""));

    return content;
}

std::list<std::string> TypeConversionGenerator::generateStructConversion(
    const std::shared_ptr<BstIdl::FTypeCollection> &object)
{
    std::list<std::string> conversions;
    if (!object)
        return conversions;

    for (const auto type : object->getTypes())
    {
        std::string tmp(StructConvertTpl);
        auto struct_obj = std::dynamic_pointer_cast<BstIdl::FStructType>(type);
        if (!struct_obj)
            continue;

        auto &gen = BstASF::GeneralServiceGeneratorExtensions::getInstance();
        tmp = replace_all(tmp, "{STRUCT_NAMESPACE}", gen.getFullName(struct_obj));
        std::list<std::string> typedef_field_types;
        std::list<std::string> get_field_values;
        std::list<std::string> get_json_objs;
        std::list<std::string> convert_to_json;
        std::list<std::string> get_val_from_json;
        std::string json_decl("json");
        std::string parameter_list("");
        int ele_index = 1;
        for (const auto field : struct_obj->getElements())
        {
            auto field_name = gen.getElementName(field);
            auto para_name = toFirstUpper(field_name);
            auto get_func = "opt.get" + para_name + "()";
            auto type_name = toLower(field_name) + "_type";
            auto ele_name = "ele" + para_name;
            auto json_name = "ele_" + para_name;

            typedef_field_types.emplace_back("typedef typename std::decay<decltype(" + get_func + ")>::type " +
                                             type_name + ";");
            get_field_values.emplace_back("auto " + ele_name + " = " + get_func + ";");
            json_decl.append(" " + json_name + ",");
            get_json_objs.emplace_back("getJson<decltype(" + ele_name + ")>(" + json_name + ", " + ele_name + ");");
            convert_to_json.emplace_back("j.push_back({\"" + para_name + "\", " + json_name + "});");
            get_val_from_json.emplace_back("auto " + field_name + " = j.at(\"" + para_name + "\").get<" + type_name +
                                           ">();");
            parameter_list.append(field_name + ", ");
            ++ele_index;
        }
        json_decl.pop_back();
        parameter_list.pop_back();
        parameter_list.pop_back();
        get_json_objs.emplace_front(json_decl + ";");

        tmp = replace_all(tmp, "{TYPEDEF_FIELD_TYPE}", join(typedef_field_types, "\n\t\t"));
        tmp = replace_one(tmp, "{STRUCT_GET_FIELD_VALUE}", join(get_field_values, "\n\t\t"));
        tmp = replace_one(tmp, "{GET_JSON_OBJ}", join(get_json_objs, "\n\t\t"));
        tmp = replace_one(tmp, "{CONVERT_TO_JSON}", join(convert_to_json, "\n\t\t"));
        tmp = replace_one(tmp, "{GET_VAL_FROM_json}", join(get_val_from_json, "\n\t\t"));
        tmp = replace_one(tmp, "{PARAMETERS_LIST}", parameter_list);
        conversions.emplace_back(tmp);
    }
    return conversions;
}
void TypeConversionGenerator::getHeaders(const std::shared_ptr<BstIdl::FInterface> &fInterface,
                                         std::list<std::string> &headers)
{
    std::list<std::string> generatedHeaders;
    std::list<std::string> libraryHeaders;
    auto &genExtention = BstASF::GeneralServiceGeneratorExtensions::getInstance();
    genExtention.generateRequiredTypeIncludes(fInterface, generatedHeaders, libraryHeaders);
    generatedHeaders.sort();
    for (auto requiredHeaderFile : generatedHeaders)
    {
        addListItem(headers, "#include <" + requiredHeaderFile + ">");
    }
    addListItem(headers, "#include <" + genExtention.getHeaderPath(fInterface) + ">");
}
} // namespace BstASF